package deepr.filter;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import deepr.core.*;
import deepr.exception.DeeprAssert;
import deepr.exception.DeeprException;
import deepr.lib.FilterLib;
import deepr.anno.SchemaType;
import deepr.anno.Type;
import deepr.lib.CastLib;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;


// todo 如果value是 object数组,则要把这个数组转成相应 类型的数组
@Slf4j
@Data
public class FilterParser {

  private DisposeContext ctx;
  private SchemaData parentSchemaData;
  private boolean meetOr = false;
  private Object orValue;
  private SchemaType schemaType;
  private Gson gson = new GsonBuilder().disableHtmlEscaping().create();
  private HashMap<String, Integer> appearCount = new HashMap<>();
  private DbFilterBehavior dbFilterBehaviour;
  private int depth = 1;
  private String convertAction = null;
  private String action = "=";

  private String handlerJsonKey = null;
  private Object handlerJsonValue = null;
  private static Map<String, Boolean> baseAction = new HashMap<String, Boolean>() {{
    put("=", true);
    put("<>", true);
    put(">", true);
    put(">=", true);
    put("<", true);
    put("<=", true);
    put("like", true);
    put("between", true);
  }};

//  private static Map<String, Boolean> allConvertAction = new HashMap<String, Boolean>() {{
//    put("unit", true);
//    put("decade", true);
//    put("hundred", true);
//    put("k", true);
//    put("th", true);
//    put("to_date", true);
//  }};


  private String joiner = " AND ";

  public Map<String, Object> getFilterParams() {
    return ctx.getFilterParams();
  }

  public FilterParser(DisposeContext ctx) {
    this.schemaType = ctx.getSchemaType();
    this.parentSchemaData = ctx.getSchemaData();
    this.ctx = ctx;
    this.dbFilterBehaviour = ctx.getDbFilterBehavior();
  }


  // [
  //  {"ts,between":[t1, t2]},
  //  {"name,like":"name"},
  //  {"recommend,>":5},
  //  {"or":[{"status": [1,2]},{"v2":[1,2,3]},{"device.id,>":5},{"id,<":2},{"device":{}}, {"collection.user.id":[]]},  device: json查询,外表关联查询
  //  {"device.name":"device_name"},
  //  {"device.id":"device_id"},
  //  {"ts,to_date":"2023-01-01 12:29:21"},
  //  {"device":{"name":xxx}}  // jsonb 查询,外键查询
  //  {"id": xxx}
  // ]
  public StringBuilder parse(String object, List<Map<String, Object>> filters) throws Exception {
    long t1 = System.currentTimeMillis();
    log.debug("filters: {}", gson.toJson(filters));
    dbFilterBehaviour.appendProperty(depth, null, object, null, null, joiner);
    iteratorFilter(null, object, filters);
    StringBuilder str = dbFilterBehaviour.getForeignFilter(this);
    HashMap<Integer, HashMap<String, FilterRelay>> depthObjectFilter = dbFilterBehaviour.getDepthObjectFilter();
    if (orValue != null) {
      this.joiner = " OR ";
      dbFilterBehaviour.resetDepthObjectFilter();
      iteratorFilter(null, object, orValue);
      str.append(" AND ").append(dbFilterBehaviour.getForeignFilter(this));
    }
    long tend = System.currentTimeMillis();
    log.debug("parse use ts:{}", tend - t1);
    log.debug("depth: {}, foreFilterMap: {}, depth:{}", depth, gson.toJson(depthObjectFilter), depthObjectFilter.size());
    log.debug("str: {}", str.toString());
    log.debug("filterParams:{}", gson.toJson(getFilterParams()));
    return str;
  }

  public StringBuilder parse(String object, Object[] filters) throws Exception {
    long t1 = System.currentTimeMillis();
    log.debug("filters: {}", gson.toJson(filters));
    dbFilterBehaviour.appendProperty(depth, null, object, null, null, joiner);
    iteratorFilter(null, object, filters);
    StringBuilder str = dbFilterBehaviour.getForeignFilter(this);
    log.debug("-----{}", dbFilterBehaviour.getDepthObjectFilter());
    HashMap<Integer, HashMap<String, FilterRelay>> depthObjectFilter = dbFilterBehaviour.getDepthObjectFilter();
    if (orValue != null) {
      this.joiner = " OR ";
      dbFilterBehaviour.resetDepthObjectFilter();
      iteratorFilter(null, object, orValue);
      str.append(" AND ").append(dbFilterBehaviour.getForeignFilter(this));
    }
    long tend = System.currentTimeMillis();
    log.debug("parse use ts:{}", tend - t1);
    log.debug("depth: {}, foreFilterMap: {}, depth:{}", depth, gson.toJson(depthObjectFilter), depthObjectFilter.size());
    log.debug("str: {}", str.toString());
    log.debug("filterParams:{}", gson.toJson(getFilterParams()));
    return str;
  }

  public void doFilter(String parent, String object, String keyStr, Object value) throws Exception {
    log.debug("parent: {}, object:{}, keyStr:{},value:{}", parent, object, keyStr, value);
    String parentName = parentSchemaData.getObject();
    SchemaData schemaData = FilterLib.getSchemaData(this, parent, object);
    if (keyStr.equals("or")) {
      Assert.isTrue(!meetOr, "or action only appear once");
      Assert.isTrue(value instanceof HashMap || value.getClass().isArray() || value instanceof LinkedList, "or value must be json or array");
      this.orValue = value;
      meetOr = true;
      return;
    }
    String[] actionInfo = keyStr.split(",");
    int actionLen = actionInfo.length;
    Assert.isTrue(0 < actionLen && actionLen < 4, keyStr + " len invalid");
    // {"device.id,>":5}
    // {"device.status,decade,>":2}
    // key = device.id
    String key = actionInfo[0];
    if (actionLen > 1) {
      action = actionInfo[1];
    }
    String[] key2 = key.split("[.]");
    int key2Len = key2.length;
    Assert.isTrue(0 < key2Len && key2Len < 6, "key len must in [1,5]");
    PropertyData propertyData;
    StringBuilder sb;
    if (actionLen == 3) {
      action = actionInfo[2];
      setConvertAction(actionInfo[1]);
    }
    if (actionLen == 2 && !isOperatorAction(actionInfo[1])) {
      action = "=";
      setConvertAction(actionInfo[1]);
    }
    switch (key2Len) {
      // {"device":{}}/{"device":[]}/{"id":xxx}, json,字段,外表
      case 1:
        String key3 = key2[0];
        propertyData = schemaData.getPropertyData(key3);
        if (propertyData.isSchema()) {
          if (depth >= 2) {
            String forProperty = propertyData.getForProperty();
            dbFilterBehaviour.appendProperty(depth, object, object, forProperty, null, joiner);
          }
          depth++;
          iteratorFilter(object, key3, value);
          depth--;
          return;
        }
        concatFilter(parentName, propertyData, object, key3, value);
        break;
      // device.director.id, device.area.name 外表json,外表的外表
      // assetCategory.location.record2.user.name10,like
      // work.roles
      default:
        String realObject = key2[0];
        propertyData = schemaData.getPropertyData(realObject);
        DeeprAssert.isTrue(propertyData != null, realObject + " for " + object + " not exists");
        if (propertyData.isSchema()) {
          if (depth >= 2) {
            String forProperty = propertyData.getForProperty();
            dbFilterBehaviour.appendProperty(depth, parentName, object, forProperty, null, joiner);
          }
          depth++;
          parentSchemaData = schemaData;
          doFilter(object, realObject, keyStr.substring(realObject.length() + 1), value);
          depth--;
          return;
        }
        Type type = propertyData.getType();
        sb = dbFilterBehaviour.jsonKey(this, object, propertyData, realObject, key2, value);
        if (type == Type.JsonArray) {
          dbFilterBehaviour.appendJsonArray(depth, parentName, object, realObject, sb, joiner);
          return;
        }
        if (type == Type.JSON) {
          dbFilterBehaviour.appendProperty(depth, parentName, object, realObject, sb, joiner);
          return;
        }
        break;
    }
  }

  // {"device":{"id":id,"name":name}}
  public void concatFilter(String parent, PropertyData propertyData, String object,
                           String key, Object value) throws
      InvocationTargetException, NoSuchMethodException, InstantiationException, ParseException, IllegalAccessException, DeeprException {
    log.debug("parent:{},object:{},key:{}, value:{}", parent, object, key, value);
    StringBuilder sb;
    Type type = propertyData.getType();
    String paramKey = getParamKey(key);
    if (parent != null) {
      paramKey = getParamKey(object + "_" + key);
    }
    if (action.equals("like")) {
      sb = dbFilterBehaviour.like(this, object, key, paramKey, value);
      dbFilterBehaviour.appendProperty(depth, parent, object, key, sb, joiner);
      return;
    }
//    setAction(action);
    boolean valueIsArray = value.getClass().isArray();
    value = handleKeyValue(propertyData, key, value);
    if (type == Type.JSON || type == Type.JsonArray) {
      sb = dbFilterBehaviour.jsonAt(this, object, key, paramKey, value);
    } else if (action.equals("between")) {
      sb = dbFilterBehaviour.between(this, object, key, key, value);
    } else if (valueIsArray && type == Type.ARRAY) {
      sb = dbFilterBehaviour.twoArray(this, object, key, paramKey, value);
    } else if (valueIsArray) {
      sb = dbFilterBehaviour.valueArray(this, object, key, paramKey, value);
    } else if (type == Type.ARRAY) {
      sb = dbFilterBehaviour.propertyArray(this, object, key, paramKey, value);
    } else
      sb = dbFilterBehaviour.restAction(this, object, key, paramKey, value);
    setConvertAction(null);
    setAction("=");
    dbFilterBehaviour.appendProperty(depth, parent, object, key, sb, joiner);
  }

  public String getParamKey(String property) {
    Integer count = appearCount.getOrDefault(property, 0);
    if (count == 0) {
      appearCount.put(property, 1);
      return property;
    }
    appearCount.put(property, count + 1);
    return property + count;
  }

  public Object[] betweenActionValueValid(String key, Object value) {
    Assert.isTrue(value.getClass().isArray(), key + "between value must be array");
    Object[] v = (Object[]) value;
    Assert.isTrue(v.length == 2, key + "between value length must be 2");
    return v;
  }

  public Object handleKeyValue(PropertyData propertyData, String key, Object value) throws
      NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, ParseException {
    Class<?> handler = propertyData.getHandler();
    if (handler != null) {
      return handler.getMethod("filter",
          FilterParser.class, String.class, Object.class, Object.class).
          invoke(handler.newInstance(), this, key, value, propertyData.getHandlerArgs());
    }
    Type type = propertyData.getType();
    if (type == Type.INTEGER) {
      return CastLib.toInteger(key, value);
    }
    if (type == Type.LONG) {
      return CastLib.toLong(key, value);
    }
    if (type == Type.DateTime) {
      return CastLib.toDateTime(key, value);
    }
    if (type == Type.DATE) {
      return CastLib.toDate(key, value);
    }
    return value;
  }


  public Object handleJsonKeyValue(PropertyData propertyData, String key, Object value) throws
      NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    Class<?> handler = propertyData.getHandler();
    if (handler != null) {
      setHandlerJsonKey(null);
      return handler.getMethod("filter",
          FilterParser.class, String.class, Object.class, Object.class).
          invoke(handler.newInstance(), this, key, value, propertyData.getHandlerArgs());
    }
    return value;
  }


  public void iteratorFilter(String parent, String object, Map<String, Object> filters) throws
      Exception {
    for (Map.Entry<String, Object> filterEntry : filters.entrySet()) {
      doFilter(parent, object, filterEntry.getKey(), filterEntry.getValue());
    }
  }

  public void iteratorFilter(String parent, String object, JSONObject filters) throws
      Exception {
    for (Map.Entry<String, Object> filterEntry : filters.entrySet()) {
      doFilter(parent, object, filterEntry.getKey(), filterEntry.getValue());
    }
  }


  public void iteratorFilter(String parent, String object, List<Map<String, Object>> filters) throws
      Exception {
    for (Map<String, Object> filter : filters) {
      iteratorFilter(parent, object, filter);
    }
  }

  public void iteratorFilter(String parent, String object, Object[] filters) throws
      Exception {
    if (filters == null)
      return;
    for (Object filter : filters) {
      iteratorFilter(parent, object, filter);
    }
  }

  public void iteratorFilter(String parent, String object, Object value) throws
      Exception {
    if (value instanceof Map) {
      iteratorFilter(parent, object, (Map<String, Object>) value);
      return;
    }
    if (value.getClass().isArray()) {
      iteratorFilter(parent, object, (Object[]) value);
      return;
    }
    if (value instanceof List) {
      iteratorFilter(parent, object, (List) value);
      return;
    }
    throw new DeeprException(object + " value type must be json or array");
  }

  public boolean isOperatorAction(String action) {
    return baseAction.getOrDefault(action, false);
  }

}
